#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import socket

import psutil

from dws.commons.common import get_cur_host_all_ip, log, dws_exec_cmd_by_type, \
    get_dws_config, dws_exec_sql_cmd_by_type
from common.common import execute_cmd
from common.util.check_user_utils import check_os_user
from common.util.cmd_utils import cmd_format
from dws.commons.const import DwsDeployType, DwsRetEnum
from dws.commons.function_tool import log_start

PORT_LINE_INDEX = 0
MIN_PORT_ARR_COUNT = 3
PORT_POS = 2
MIN_DETAIL_ARR_COUNT = 5
CONF_DIR_POS = 4
HOST_NAME_POS = 1
HOST_IP_POS = 2


class DwsCommon:

    def __init__(self, user_name, env_file, pid=""):
        self._user_name = user_name
        self._env_file = env_file
        self._pid = pid
        self._cluster_status = []
        self._cluster_status_detail = []
        self._cluster_all_info = []
        self._deploy_type = int(get_dws_config().get("deployType", DwsDeployType.DEPLOY_IN_SERVER.value))

    @staticmethod
    def filter_pids(filter_name: str, exclude_name: str = None):
        """
        根据指定的名称来筛选出包含filter_name但不是不包含filter_name的进程pid
        """
        log.info(f"Filter name: {filter_name}, exclude name: {exclude_name}.")
        ret_pids = []
        for pid in psutil.pids():
            try:
                cmd_line = psutil.Process(pid).cmdline()
            except Exception as err:
                log.warn(f"Get process err: {err}.")
                continue
            cmd_line_str = ",".join(cmd_line)
            if exclude_name and exclude_name in cmd_line_str:
                continue
            if filter_name in cmd_line_str:
                ret_pids.append(pid)
        log.info(f"Get pids: {ret_pids}.")
        return ret_pids

    @staticmethod
    def get_hostname():
        return socket.gethostname()

    @log_start()
    def get_all_cluster_node_info(self) -> bool:
        ret, out_info = dws_exec_cmd_by_type(self._user_name, self._env_file, "cm_ctl view")
        if not ret:
            log.error(f"DWS exec cmd failed with [{out_info}].")
            return False
        self._cluster_all_info = [item.strip() for item in out_info.split('\n') if item.strip() != ""]
        return True

    @log_start()
    def filter_cluster_node_info(self, filter_str) -> list:
        log.info(f"Filter string: {filter_str}.")
        ret_lit = []
        if not self._cluster_all_info and not self.get_all_cluster_node_info():
            log.error(f"Get all cluster node info failed, PID: {self._pid}.")
            return ret_lit

        filter_arr = [item for item in self._cluster_all_info if filter_str in item]
        for line in filter_arr:
            tmp_list = line.split(":")
            if len(tmp_list) == 2:
                ret_lit.append(tmp_list[1].strip())
            else:
                log.warn(f"Invalid line:{line}.")
        return ret_lit

    def exec_query_status(self):
        ret, out_info = dws_exec_cmd_by_type(self._user_name, self._env_file, "gs_om -t status")
        if not ret:
            log.error(f"Get cluster status error with return: {out_info}.")
            return False, []
        self._cluster_status = [item.strip() for item in out_info.split('\n') if item.strip() != ""]
        return True, self._cluster_status

    def exec_query_status_detail(self):
        ret, out_info = dws_exec_cmd_by_type(self._user_name, self._env_file, "gs_om -t status --detail")
        if not ret:
            log.error(f"Get cluster status error with return: {out_info}.")
            return False, []
        self._cluster_status_detail = [item.strip() for item in out_info.split('\n') if item.strip() != ""]
        return True, self._cluster_status_detail

    @log_start()
    def get_all_cn_hostip(self) -> set:
        """
        获取当前集群的所有带CN实例节点的ip地址
        返回值：Set
        """
        cn_host_ip = self.filter_cluster_node_info("coordinateListenIP")
        ssh_channel_ip = self.filter_cluster_node_info("sshChannel")
        new_cn_host_ip = set(cn_host_ip) & set(ssh_channel_ip)
        return new_cn_host_ip

    @log_start()
    def get_all_cn_ip_port(self) -> list:
        """
        函数功能：获取当前集群的所有CN实例节点的IP地址和数据库登录端口
        返回值：list
        """
        ret_list = []
        cn_host_ip = self.filter_cluster_node_info("coordinateListenIP")
        ssh_channel_ip = self.filter_cluster_node_info("sshChannel")
        new_cn_host_ip = set(cn_host_ip) & set(ssh_channel_ip)
        cn_db_port = self.filter_cluster_node_info("coordinatePort")
        if not new_cn_host_ip or not cn_db_port or (len(new_cn_host_ip) != len(cn_db_port)):
            log.error(f"Get CN host ip [{new_cn_host_ip}] or db prot error [{cn_db_port}], PID: {self._pid}.")
            return ret_list

        for tmp_ip, tmp_port in zip(new_cn_host_ip, cn_db_port):
            ret_list.append([tmp_ip, tmp_port])

        return ret_list

    @log_start()
    def is_cur_node_cn(self):
        """
        判断当前节点是有CN实例
        """
        return not self.get_all_cn_hostip().isdisjoint(get_cur_host_all_ip())

    @log_start()
    def get_db_port(self):
        """
        函数功能：获取当前节点登录数据库的CN实例的端口
        返回值：查询成功返回端口，查询失败返回空
        """
        if not self.is_cur_node_cn():
            log.error(f"Get db port error due to current node isn't CN, PID: {self._pid}.")
            return ""
        cur_node_ip = (set(get_cur_host_all_ip()) & self.get_all_cn_hostip()).pop()
        all_cn_ip_port = self.get_all_cn_ip_port()
        if not cur_node_ip or not all_cn_ip_port:
            log.error(f"Get cur_node_ip [{cur_node_ip}] or all_cn_ip_port failed [{all_cn_ip_port}], PID: {self._pid}.")
            return ""

        for tmp_ip, tmp_port in all_cn_ip_port:
            if cur_node_ip == tmp_ip:
                return tmp_port

        log.error(f"Get db port failed due to cannot find cur node ip in all cn ip port , PID: {self._pid}.")
        return ""

    def exec_gaussdb_sql_cmd(self, db_name, sql_cmd):
        """
        函数功能：登录指定的数据库，执行数据库命令，sql命令
        返回值：成功或者失败，执行命令返回的结果数组
        """
        all_cn_ip_port = self.get_all_cn_ip_port()
        if not all_cn_ip_port:
            log.error(f"Get all_cn_ip_port [{all_cn_ip_port}] failed, PID: {self._pid}.")
            return False, []

        db_port = self.get_db_port()
        log.debug(f"Exec gaussdb sql_cmd: {sql_cmd}")
        if self.is_cur_node_cn() and db_port:
            cmd = f"gsql postgresql://:{db_port}/{db_name}?application_name='OM' -r"
            ret, out_info = dws_exec_sql_cmd_by_type(self._user_name, self._env_file, cmd, sql_cmd)
            if ret and "ERROR" not in out_info:
                log.info(f"Exec sql cmd in current node success, PID: {self._pid}.")
                return True, [item for item in out_info.split("\n") if item.strip() != ""]

        # 替换掉双引号，不然执行命令会报错
        for tmp_ip, tmp_port in all_cn_ip_port:
            # 登录数据库，执行对应的sql命令
            if self._deploy_type == DwsDeployType.DEPLOY_IN_SERVER.value:
                cmd = f"ssh {tmp_ip} \\\"source {self._env_file} && gsql postgresql://:{tmp_port}/{db_name}?" \
                      f"application_name='OM' -r\\\""
            else:
                cmd = f"ssh {tmp_ip} \\\"source {self._env_file};source /etc/profile;gsql postgresql://:{tmp_port}/" \
                      f"{db_name}?application_name='OM' -r\\\""
            ret, out_info = dws_exec_sql_cmd_by_type(self._user_name, self._env_file, cmd, sql_cmd)
            if ret and "ERROR" not in out_info:
                log.info(f"Exec sql cmd in remote node [{tmp_ip}] success, PID: {self._pid}.")
                # 返回时要去除空行和空元素
                return True, [item for item in out_info.split("\n") if item.strip() != ""]
            log.error(f"Exec sql cmd error in remote node [{tmp_ip}] " \
                      f"with return: {out_info}, db_name: {db_name}, PID: {self._pid}.")

        return False, []

    def exec_gaussdb_sql_file(self, db_name, sql_file):
        """
        函数功能：登录指定的数据库, 执行对应的sql文件
        返回值：成功或者失败，执行命令返回的结果数组
        """
        db_port = self.get_db_port()
        if not db_port:
            log.error(f"Get db port failed, PID: {self._pid}.")
            return False, []

        # 登录数据库，执行对应的sql命令
        cmd = f"gsql postgresql://:{db_port}/{db_name}?application_name='OM' -r"
        sql_cmd = f"\\i {sql_file}"
        ret, out_info = dws_exec_sql_cmd_by_type(self._user_name, self._env_file, cmd, sql_cmd)
        if not ret or "ERROR" in out_info:
            log.error(f"Exec sql file error with return: {out_info}, db_name: {db_name}, PID: {self._pid}.")
            return False, []

        # 清除空元素
        log.info(f"Exec gaussdb sql file suc, PID: {self._pid}.")
        original_lines = [item for item in out_info.split("\n") if item.strip() != ""]
        return True, original_lines
